/*
 * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
 * Copyright (C) 2013 Armink <armink.ztl@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * File: $Id: mbrtu_m.c,v 1.60 2013/08/20 11:18:10 Armink Add Master Functions $
 */

/* ----------------------- System includes ----------------------------------*/
#include "stdlib.h"
#include "string.h"

/* ----------------------- Platform includes --------------------------------*/
#include "port.h"

/* ----------------------- Modbus includes ----------------------------------*/

#include "mb.h"
#include "mb_m.h"
#include "mbconfig.h"
#include "mbframe.h"
#include "mbproto.h"
#include "mbfunc.h"

#include "user_mb_app.h"

#include "mbport.h"
#if MB_MASTER_RTU_ENABLED == 1
#include "mbrtu.h"
#endif
#if MB_MASTER_ASCII_ENABLED == 1
#include "mbascii.h"
#endif
#if MB_MASTER_TCP_ENABLED == 1
#include "mbtcp.h"
#endif

#if MB_MASTER_RTU_ENABLED > 0 || MB_MASTER_ASCII_ENABLED > 0

#ifndef MB_PORT_HAS_CLOSE
#define MB_PORT_HAS_CLOSE 0
#endif

/* ----------------------- Static variables ---------------------------------*/

static UCHAR ucMBMasterDestAddress[MB_MASTER_NUM];
static BOOL xMBRunInMasterMode[MB_MASTER_NUM] = {FALSE};
static eMBMasterErrorEventType eMBMasterCurErrorType[MB_MASTER_NUM];

static enum { STATE_ENABLED, STATE_DISABLED, STATE_NOT_INITIALIZED } eMBState[MB_MASTER_NUM] = {STATE_NOT_INITIALIZED};

/* Functions pointer which are initialized in eMBInit( ). Depending on the
 * mode (RTU or ASCII) the are set to the correct implementations.
 * Using for Modbus Master,Add by Armink 20130813
 */
static peMBFrameSend_Master peMBMasterFrameSendCur;
static pvMBFrameStart_Master pvMBMasterFrameStartCur;
static peMBFrameReceive_Master peMBMasterFrameReceiveCur;
static pvMBFrameClose_Master pvMBMasterFrameCloseCur;

/* Callback functions required by the porting layer. They are called when
 * an external event has happend which includes a timeout or the reception
 * or transmission of a character.
 * Using for Modbus Master,Add by Armink 20130813
 */
BOOL (*pxMBMasterFrameCBByteReceived)(UCHAR ucMB_Number);
BOOL (*pxMBMasterFrameCBTransmitterEmpty)(UCHAR ucMB_Number);
BOOL (*pxMBMasterPortCBTimerExpired)(UCHAR ucMB_Number);

BOOL (*pxMBMasterFrameCBReceiveFSMCur)(void);
BOOL (*pxMBMasterFrameCBTransmitFSMCur)(void);

/* An array of Modbus functions handlers which associates Modbus function
 * codes with implementing functions.
 */
static xMBFunctionHandler_Master xMasterFuncHandlers[MB_FUNC_HANDLERS_MAX] = {
#if MB_FUNC_OTHER_REP_SLAVEID_ENABLED > 0
    {MB_FUNC_OTHER_REPORT_SLAVEID, eMBMasterFuncReportSlaveID},
#endif
#if MB_FUNC_READ_INPUT_ENABLED > 0
    {MB_FUNC_READ_INPUT_REGISTER, eMBMasterFuncReadInputRegister},
#endif
#if MB_FUNC_READ_HOLDING_ENABLED > 0
    {MB_FUNC_READ_HOLDING_REGISTER, eMBMasterFuncReadHoldingRegister},
#endif
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED > 0
    {MB_FUNC_WRITE_MULTIPLE_REGISTERS, eMBMasterFuncWriteMultipleHoldingRegister},
#endif
#if MB_FUNC_WRITE_HOLDING_ENABLED > 0
    {MB_FUNC_WRITE_REGISTER, eMBMasterFuncWriteHoldingRegister},
#endif
#if MB_FUNC_READWRITE_HOLDING_ENABLED > 0
    {MB_FUNC_READWRITE_MULTIPLE_REGISTERS, eMBMasterFuncReadWriteMultipleHoldingRegister},
#endif
#if MB_FUNC_READ_COILS_ENABLED > 0
    {MB_FUNC_READ_COILS, eMBMasterFuncReadCoils},
#endif
#if MB_FUNC_WRITE_COIL_ENABLED > 0
    {MB_FUNC_WRITE_SINGLE_COIL, eMBMasterFuncWriteCoil},
#endif
#if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED > 0
    {MB_FUNC_WRITE_MULTIPLE_COILS, eMBMasterFuncWriteMultipleCoils},
#endif
#if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED > 0
    {MB_FUNC_READ_DISCRETE_INPUTS, eMBMasterFuncReadDiscreteInputs},
#endif
};

/* ----------------------- Start implementation -----------------------------*/
eMBErrorCode eMBMasterInit(eMBMode eMode, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity, UCHAR ucMB_Number)
{
    eMBErrorCode eStatus = MB_ENOERR;

    switch (eMode)
    {
#if MB_MASTER_RTU_ENABLED > 0
        case MB_RTU:
            pvMBMasterFrameStartCur           = eMBMasterRTUStart;
            peMBMasterFrameSendCur            = eMBMasterRTUSend;
            peMBMasterFrameReceiveCur         = eMBMasterRTUReceive;
            pvMBMasterFrameCloseCur           = MB_PORT_HAS_CLOSE ? vMBMasterPortClose : NULL;
            pxMBMasterFrameCBByteReceived     = xMBMasterRTUReceiveFSM;
            pxMBMasterFrameCBTransmitterEmpty = xMBMasterRTUTransmitFSM;
            pxMBMasterPortCBTimerExpired      = xMBMasterRTUTimerExpired;

            eStatus = eMBMasterRTUInit(ucPort, ulBaudRate, eParity, ucMB_Number);
            break;
#endif
#if MB_MASTER_ASCII_ENABLED > 0
        case MB_ASCII:
            break;
#endif
        default:
            eStatus = MB_EINVAL;
            break;
    }

    if (eStatus == MB_ENOERR)
    {
        if (!xMBMasterPortEventInit(ucMB_Number))
        {
            /* port dependent event module initalization failed. */
            eStatus = MB_EPORTERR;
        }
        else
        {
            eMBState[ucMB_Number] = STATE_DISABLED;
        }
        /* initialize the OS resource for modbus master. */
        vMBMasterOsResInit(ucMB_Number);
    }
    return eStatus;
}

eMBErrorCode eMBMasterClose(UCHAR ucMB_Number)
{
    eMBErrorCode eStatus = MB_ENOERR;

    if (eMBState[ucMB_Number] == STATE_DISABLED)
    {
        if (pvMBMasterFrameCloseCur != NULL)
        {
            pvMBMasterFrameCloseCur(ucMB_Number);
        }
    }
    else
    {
        eStatus = MB_EILLSTATE;
    }
    return eStatus;
}

eMBErrorCode eMBMasterEnable(UCHAR ucMB_Number)
{
    eMBErrorCode eStatus = MB_ENOERR;

    if (eMBState[ucMB_Number] == STATE_DISABLED)
    {
        /* Activate the protocol stack. */
        pvMBMasterFrameStartCur(ucMB_Number);
        eMBState[ucMB_Number] = STATE_ENABLED;
    }
    else
    {
        eStatus = MB_EILLSTATE;
        rt_kprintf("master enable failed!\n");
    }
    return eStatus;
}

eMBErrorCode eMBMasterDisable(UCHAR ucMB_Number)
{
    eMBErrorCode eStatus;

    if (eMBState[ucMB_Number] == STATE_ENABLED)
    {
        eMBState[ucMB_Number] = STATE_DISABLED;
        eStatus               = MB_ENOERR;
    }
    else if (eMBState[ucMB_Number] == STATE_DISABLED)
    {
        eStatus = MB_ENOERR;
    }
    else
    {
        eStatus = MB_EILLSTATE;
    }
    return eStatus;
}

eMBErrorCode eMBMasterPoll(void)
{
    static UCHAR *ucMBFrame;
    static UCHAR ucRcvAddress;
    static UCHAR ucFunctionCode;
    static USHORT usLength;
    static eMBException eException;

    int i, j;
    eMBErrorCode eStatus = MB_ENOERR;
    eMBMasterEventType eEvent;
    eMBMasterErrorEventType errorType;

    UCHAR MB_MasterPoll_0 = MB_MASTER_0;

    /* Check if the protocol stack is ready. */
    if (eMBState[MB_MasterPoll_0] != STATE_ENABLED)
    {
        return MB_EILLSTATE;
    }

    /* Check if there is a event available. If not return control to caller.
     * Otherwise we will handle the event. */
    if (xMBMasterPortEventGet(&eEvent, MB_MasterPoll_0) == TRUE)
    {
        switch (eEvent)
        {
            case EV_MASTER_READY:
                break;
            case EV_MASTER_FRAME_RECEIVED:

                eStatus = peMBMasterFrameReceiveCur(&ucRcvAddress, &ucMBFrame, &usLength, MB_MasterPoll_0);
                /* Check if the frame is for us. If not ,send an error process event. */
                if ((eStatus == MB_ENOERR) && (ucRcvAddress == ucMBMasterGetDestAddress(MB_MasterPoll_0)))
                {
                    (void)xMBMasterPortEventPost(EV_MASTER_EXECUTE, MB_MasterPoll_0);
                }
                else
                {
                    vMBMasterSetErrorType(EV_ERROR_RECEIVE_DATA, MB_MasterPoll_0);
                    (void)xMBMasterPortEventPost(EV_MASTER_ERROR_PROCESS, MB_MasterPoll_0);
                }
                break;

            case EV_MASTER_EXECUTE:
                ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF];

                eException = MB_EX_ILLEGAL_FUNCTION;
                /* If receive frame has exception .The receive function code highest bit is 1.*/
                if (ucFunctionCode >> 7)
                {
                    eException = (eMBException)ucMBFrame[MB_PDU_DATA_OFF];
                }
                else
                {
                    for (i = 0; i < MB_FUNC_HANDLERS_MAX; i++)
                    {
                        /* No more function handlers registered. Abort. */
                        if (xMasterFuncHandlers[i].ucFunctionCode == 0)
                        {
                            break;
                        }
                        else if (xMasterFuncHandlers[i].ucFunctionCode == ucFunctionCode)
                        {
                            vMBMasterSetCBRunInMasterMode(TRUE, MB_MasterPoll_0);
                            /* If master request is broadcast,
                             * the master need execute function for all slave.
                             */
                            if (xMBMasterRequestIsBroadcast(MB_MasterPoll_0))
                            {
                                usLength = usMBMasterGetPDUSndLength(MB_MasterPoll_0);
                                for (j = 1; j <= MB_MASTER_TOTAL_SLAVE_NUM; j++)
                                {
                                    vMBMasterSetDestAddress(j, MB_MasterPoll_0);
                                    eException =
                                        xMasterFuncHandlers[i].pxHandler(ucMBFrame, &usLength, MB_MasterPoll_0);
                                }
                            }
                            else
                            {
                                eException = xMasterFuncHandlers[i].pxHandler(ucMBFrame, &usLength, MB_MasterPoll_0);
                            }
                            vMBMasterSetCBRunInMasterMode(FALSE, MB_MasterPoll_0);
                            break;
                        }
                    }
                }
                /* If master has exception ,Master will send error process.Otherwise the Master is idle.*/
                if (eException != MB_EX_NONE)
                {
                    vMBMasterSetErrorType(EV_ERROR_EXECUTE_FUNCTION, MB_MasterPoll_0);
                    (void)xMBMasterPortEventPost(EV_MASTER_ERROR_PROCESS, MB_MasterPoll_0);
                }
                else
                {
                    vMBMasterCBRequestScuuess(MB_MasterPoll_0);
                    vMBMasterRunResRelease(MB_MasterPoll_0);
                }
                break;

            case EV_MASTER_FRAME_SENT:
                /* Master is busy now. */
                vMBMasterGetPDUSndBuf(&ucMBFrame, MB_MasterPoll_0);
                eStatus = peMBMasterFrameSendCur(ucMBMasterGetDestAddress(MB_MasterPoll_0), ucMBFrame,
                                                 usMBMasterGetPDUSndLength(MB_MasterPoll_0), MB_MasterPoll_0);
                break;

            case EV_MASTER_ERROR_PROCESS:
                /* Execute specified error process callback function. */
                errorType = eMBMasterGetErrorType(MB_MasterPoll_0);
                vMBMasterGetPDUSndBuf(&ucMBFrame, MB_MasterPoll_0);
                switch (errorType)
                {
                    case EV_ERROR_RESPOND_TIMEOUT:
                        vMBMasterErrorCBRespondTimeout(ucMBMasterGetDestAddress(MB_MasterPoll_0), ucMBFrame,
                                                       usMBMasterGetPDUSndLength(MB_MasterPoll_0), MB_MasterPoll_0);
                        break;
                    case EV_ERROR_RECEIVE_DATA:
                        vMBMasterErrorCBReceiveData(ucMBMasterGetDestAddress(MB_MasterPoll_0), ucMBFrame,
                                                    usMBMasterGetPDUSndLength(MB_MasterPoll_0), MB_MasterPoll_0);
                        break;
                    case EV_ERROR_EXECUTE_FUNCTION:
                        vMBMasterErrorCBExecuteFunction(ucMBMasterGetDestAddress(MB_MasterPoll_0), ucMBFrame,
                                                        usMBMasterGetPDUSndLength(MB_MasterPoll_0), MB_MasterPoll_0);
                        break;
                }
                vMBMasterRunResRelease(MB_MasterPoll_0);
                break;
        }
    }
    return MB_ENOERR;
}

eMBErrorCode eMBMasterPoll_01(void)
{
    static UCHAR *ucMBFrame;
    static UCHAR ucRcvAddress;
    static UCHAR ucFunctionCode;
    static USHORT usLength;
    static eMBException eException;

    int i, j;
    eMBErrorCode eStatus = MB_ENOERR;
    eMBMasterEventType eEvent;
    eMBMasterErrorEventType errorType;

    static UCHAR MB_MasterPoll_0 = MB_MASTER_1;

    /* Check if the protocol stack is ready. */
    if (eMBState[MB_MasterPoll_0] != STATE_ENABLED)
    {
        return MB_EILLSTATE;
    }

    /* Check if there is a event available. If not return control to caller.
     * Otherwise we will handle the event. */
    if (xMBMasterPortEventGet(&eEvent, MB_MasterPoll_0) == TRUE)
    {
        switch (eEvent)
        {
            case EV_MASTER_READY:
                break;
            case EV_MASTER_FRAME_RECEIVED:

                eStatus = peMBMasterFrameReceiveCur(&ucRcvAddress, &ucMBFrame, &usLength, MB_MasterPoll_0);
                /* Check if the frame is for us. If not ,send an error process event. */
                if ((eStatus == MB_ENOERR) && (ucRcvAddress == ucMBMasterGetDestAddress(MB_MasterPoll_0)))
                {
                    (void)xMBMasterPortEventPost(EV_MASTER_EXECUTE, MB_MasterPoll_0);
                }
                else
                {
                    vMBMasterSetErrorType(EV_ERROR_RECEIVE_DATA, MB_MasterPoll_0);
                    (void)xMBMasterPortEventPost(EV_MASTER_ERROR_PROCESS, MB_MasterPoll_0);
                }
                break;

            case EV_MASTER_EXECUTE:
                ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF];

                eException = MB_EX_ILLEGAL_FUNCTION;
                /* If receive frame has exception .The receive function code highest bit is 1.*/
                if (ucFunctionCode >> 7)
                {
                    eException = (eMBException)ucMBFrame[MB_PDU_DATA_OFF];
                }
                else
                {
                    for (i = 0; i < MB_FUNC_HANDLERS_MAX; i++)
                    {
                        /* No more function handlers registered. Abort. */
                        if (xMasterFuncHandlers[i].ucFunctionCode == 0)
                        {
                            break;
                        }
                        else if (xMasterFuncHandlers[i].ucFunctionCode == ucFunctionCode)
                        {
                            vMBMasterSetCBRunInMasterMode(TRUE, MB_MasterPoll_0);
                            /* If master request is broadcast,
                             * the master need execute function for all slave.
                             */
                            if (xMBMasterRequestIsBroadcast(MB_MasterPoll_0))
                            {
                                usLength = usMBMasterGetPDUSndLength(MB_MasterPoll_0);
                                for (j = 1; j <= MB_MASTER_TOTAL_SLAVE_NUM; j++)
                                {
                                    vMBMasterSetDestAddress(j, MB_MasterPoll_0);
                                    eException =
                                        xMasterFuncHandlers[i].pxHandler(ucMBFrame, &usLength, MB_MasterPoll_0);
                                }
                            }
                            else
                            {
                                eException = xMasterFuncHandlers[i].pxHandler(ucMBFrame, &usLength, MB_MasterPoll_0);
                            }
                            vMBMasterSetCBRunInMasterMode(FALSE, MB_MasterPoll_0);
                            break;
                        }
                    }
                }
                /* If master has exception ,Master will send error process.Otherwise the Master is idle.*/
                if (eException != MB_EX_NONE)
                {
                    vMBMasterSetErrorType(EV_ERROR_EXECUTE_FUNCTION, MB_MasterPoll_0);
                    (void)xMBMasterPortEventPost(EV_MASTER_ERROR_PROCESS, MB_MasterPoll_0);
                }
                else
                {
                    vMBMasterCBRequestScuuess(MB_MasterPoll_0);
                    vMBMasterRunResRelease(MB_MasterPoll_0);
                }
                break;

            case EV_MASTER_FRAME_SENT:
                /* Master is busy now. */
                vMBMasterGetPDUSndBuf(&ucMBFrame, MB_MasterPoll_0);
                eStatus = peMBMasterFrameSendCur(ucMBMasterGetDestAddress(MB_MasterPoll_0), ucMBFrame,
                                                 usMBMasterGetPDUSndLength(MB_MasterPoll_0), MB_MasterPoll_0);
                break;

            case EV_MASTER_ERROR_PROCESS:
                /* Execute specified error process callback function. */
                errorType = eMBMasterGetErrorType(MB_MasterPoll_0);
                vMBMasterGetPDUSndBuf(&ucMBFrame, MB_MasterPoll_0);
                switch (errorType)
                {
                    case EV_ERROR_RESPOND_TIMEOUT:
                        vMBMasterErrorCBRespondTimeout(ucMBMasterGetDestAddress(MB_MasterPoll_0), ucMBFrame,
                                                       usMBMasterGetPDUSndLength(MB_MasterPoll_0), MB_MasterPoll_0);
                        break;
                    case EV_ERROR_RECEIVE_DATA:
                        vMBMasterErrorCBReceiveData(ucMBMasterGetDestAddress(MB_MasterPoll_0), ucMBFrame,
                                                    usMBMasterGetPDUSndLength(MB_MasterPoll_0), MB_MasterPoll_0);
                        break;
                    case EV_ERROR_EXECUTE_FUNCTION:
                        vMBMasterErrorCBExecuteFunction(ucMBMasterGetDestAddress(MB_MasterPoll_0), ucMBFrame,
                                                        usMBMasterGetPDUSndLength(MB_MasterPoll_0), MB_MasterPoll_0);
                        break;
                }
                vMBMasterRunResRelease(MB_MasterPoll_0);
                break;
        }
    }
    return MB_ENOERR;
}

/* Get whether the Modbus Master is run in master mode.*/
BOOL xMBMasterGetCBRunInMasterMode(UCHAR ucMB_Number)
{
    return xMBRunInMasterMode[ucMB_Number];
}
/* Set whether the Modbus Master is run in master mode.*/
void vMBMasterSetCBRunInMasterMode(BOOL IsMasterMode, UCHAR ucMB_Number)
{
    xMBRunInMasterMode[ucMB_Number] = IsMasterMode;
}
/* Get Modbus Master send destination address. */
UCHAR ucMBMasterGetDestAddress(UCHAR ucMB_Number)
{
    return ucMBMasterDestAddress[ucMB_Number];
}
/* Set Modbus Master send destination address. */
void vMBMasterSetDestAddress(UCHAR Address, UCHAR ucMB_Number)
{
    ucMBMasterDestAddress[ucMB_Number] = Address;
}
/* Get Modbus Master current error event type. */
eMBMasterErrorEventType eMBMasterGetErrorType(UCHAR ucMB_Number)
{
    return eMBMasterCurErrorType[ucMB_Number];
}
/* Set Modbus Master current error event type. */
void vMBMasterSetErrorType(eMBMasterErrorEventType errorType, UCHAR ucMB_Number)
{
    eMBMasterCurErrorType[ucMB_Number] = errorType;
}

#endif
